﻿using System;
using System.Collections.Generic;
using Coding4Fun.Maelstrom.Common;
using Coding4Fun.Maelstrom.Communication;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Connectivity;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Input;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace Coding4Fun.Maelstrom.Tablet
{
	/// <summary>
	/// An empty page that can be used on its own or navigated to within a Frame.
	/// </summary>
	public sealed partial class Controller : Page
	{
		private SocketClient _socketClient;
		private uint _pointerIdPosition;
		private bool _connected;
		private readonly Player _player;

		private readonly List<Blade> _blades = new List<Blade>();
		private readonly Random _rand = new Random();
		private Point _contact;
		private bool _isClosing;
		private bool _inGame;
		private int _testScore1;
		private int _testScore2;

		// THESE ARE THE VALUES TO CONFINE TO GRID, BUT NEED TO BE ADJUSTED WITH PADDLE RADIUS
		//private readonly Point _upperLeftAnchor = new Point(210.0, 40.0);
		//private readonly Point _upperRightAnchor = new Point(1710.0, 40.0);
		//private readonly Point _lowerLeftAnchor = new Point(210.0, 1040.0);
		//private readonly Point _lowerRightAnchor = new Point(1710.0, 1040.0);
		//private const double Top = 40;
		//private const double Bottom = 1040;
		//private const double LeftInner = 210;
		//private const double RightInner = 1710;
		//private const double Left = 10;
		//private const double Right = 1910;

		// AFTER ADDING 200 FOR THE PADDLE RADIUS ALL AROUND
		private readonly Point _upperLeftAnchor = new Point(495.0, 40.0);
		private readonly Point _upperRightAnchor = new Point(1430.0, 40.0);	
		private readonly Point _lowerLeftAnchor = new Point(495.0, 1040.0);
		private readonly Point _lowerRightAnchor = new Point(1430.0, 1040.0);
		
		private const double Top = 240;
		private const double Bottom = 840;
		private const double LeftInner = 410;
		private const double RightInner = 1310;
		private const double Left = 210;
		private const double Right = 1710;

		private const double FullTop = 40;
		private const double FullBottom = 1040;
		private const double FullLeft = 10;
		private const double FullRight = 1910;

		private const double PaddleRadius = 200.0;

		private const int NumBlades = 54;

		private const int RightDigit1Single = 1198;
		private const int RightDigit2Single = 1402;

		private const int RightDigit1Double = 1096;
		private const int RightDigit2Double = 1300;

		private bool _waiting;

		public Controller()
		{
			this.InitializeComponent();
			CompositionTarget.Rendering += CompositionTarget_Rendering;
			canvas_root.PointerPressed += canvas_root_PointerPressed;

			CoreWindow.GetForCurrentThread().KeyDown += Main_KeyDown;

			this.IpAddress.Text = Settings.GameConsoleIp_Player1;

			_player = GetPlayer();
			if(_player == Player.Invalid)
				new MessageDialog("I have no idea who I am....make sure the IP addresses are correct in Settings.cs!").ShowAsync();
		}

		/// <summary>
		/// Invoked when this page is about to be displayed in a Frame.
		/// </summary>
		/// <param name="e">Event data that describes how this page was reached.  The Parameter
		/// property is typically used to configure the page.</param>
		protected override void OnNavigatedTo(NavigationEventArgs e)
		{
			canvas_blades.Children.Clear();
			_blades.Clear();
			for (int i = 0; i < NumBlades; i++)
			{
				Blade b = new Blade { X = 950.0, Y = 530.0 };
				int id = _rand.Next(0, 5);
				b.SetBlade(id);
				b.Speed = .1 + id * .1;
				SeedBlade(b);
				_blades.Add(b);
				canvas_blades.Children.Add(b);
			}
		}

		private void Start()
		{
			imgTextStart.Visibility = Visibility.Collapsed;
			imgTextWaiting.Visibility = Visibility.Collapsed;
			_isClosing = true;
			_inGame = true;
			LeaveWaiting.Completed += LeaveWaiting_Completed;
			LeaveWaiting.Begin();

			_testScore1 = 0;
			_testScore2 = 0;

			SetScore(_testScore1, _testScore2);
		}

		private void Reset()
		{
			_isClosing = false;
			_inGame = false;
			_waiting = false;
			imgTextStart.Visibility = Visibility.Visible;
			imgTextWaiting.Visibility = Visibility.Collapsed;

			foreach (Blade b in _blades)
				SeedBlade(b);

			LeaveWaiting.Stop();
		}

		private async void canvas_root_PointerPressed(object sender, PointerRoutedEventArgs e)
		{
			if(_inGame)
				return;

			if(_waiting)
				Reset();
			else
			{
				imgTextStart.Visibility = Visibility.Collapsed;
				imgTextWaiting.Visibility = Visibility.Visible;

				UserInputPacket packet = new UserInputPacket { Command = UserInputCommand.StartGame };
				if(_socketClient != null)
					await _socketClient.WritePacketAsync(packet);

				_waiting = true;
			}
		}

		void LeaveWaiting_Completed(object sender, object e)
		{
			_isClosing = false;
			_inGame = true;
		}

		void CompositionTarget_Rendering(object sender, object e)
		{
			if(_inGame)
			{
				paddle.Update();
			}
			else if(_isClosing)
			{
				foreach (Blade b in _blades)
					b.UpdateExit();
			}
			else
			{
				foreach (Blade b in _blades)
					b.Update();
			}
		}

		private void SeedBlade(Blade b)
		{
			b.SetScale(1.0 + _rand.NextDouble() * 1.6, .5 + _rand.NextDouble());
			b.SetRotation(_rand.NextDouble() * 360, _rand.NextDouble() * 360, _rand.NextDouble() * 360);
		}

		private void MovePaddle(double x, double y)
		{
			_contact.X = x;
			_contact.Y = y;

			// constrain to top and bottom first
			if (_contact.Y < Top)
				_contact.Y = Top;

			if (_contact.Y > Bottom)
				_contact.Y = Bottom;

			// based on X value, constraint on Left / Right Slanted Walls
			if (_contact.X < LeftInner)
			{
				if (_contact.Y < 510)
					_contact = ConstrainToAngle(_upperLeftAnchor, 135.0, _contact, 1.0);
				else
					_contact = ConstrainToAngle(_lowerLeftAnchor, -135.0, _contact, -1.0);
			}
			else if (_contact.X > RightInner)
			{
				if (_contact.Y < 510)
					_contact = ConstrainToAngle(_upperRightAnchor, 45.0, _contact, -1.0);
				else
					_contact = ConstrainToAngle(_lowerRightAnchor, -45.0, _contact, 1.0);
			}

			if (_contact.X < Left) _contact.X = Left;
			if (_contact.X > Right) _contact.X = Right;

			// UPDATE PUCK
			paddle.SetPosition(_contact.X, _contact.Y);
			//paddle.X = _contact.X;
			//paddle.Y = _contact.Y;
		}

		private Point ConstrainToAngle(Point anchor, double angle, Point contact, double xNormal)
		{
			Point finalPosition;

			double radians = angle * Math.PI / 180.0;
			double cos = Math.Cos(radians);
			double sin = Math.Sin(radians);
			double x1 = _contact.X - anchor.X;
			double y1 = _contact.Y - anchor.Y;
			double rotX = cos * x1 + sin * y1;
			double rotY = cos * y1 - sin * x1;

			if (xNormal * rotY > 0)
				rotY = 0;

			if (rotX < 0.0)
				rotX = 0.0;

			finalPosition.X = cos * rotX - sin * rotY;
			finalPosition.Y = cos * rotY + sin * rotX;
			finalPosition.X += anchor.X;
			finalPosition.Y += anchor.Y;

			return finalPosition;
		}

		private void paddle_PointerEntered(object sender, PointerRoutedEventArgs e)
		{
			if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
				return;

			if (_pointerIdPosition == 0)
			{
				PointerPoint point = e.GetCurrentPoint(playingField);
				_pointerIdPosition = point.PointerId;
			}
		}

		private void paddle_PointerMoved(object sender, PointerRoutedEventArgs e)
		{
			PointerPoint point = e.GetCurrentPoint(playingField);

			// if we're single touch, and the pointer IDs match up
			// OR we're not single touch
			if((Settings.ConstrainSingleTouch && (point.PointerId == _pointerIdPosition || e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse))
				|| !Settings.ConstrainSingleTouch)
			{
				MovePaddle(point.Position.X, point.Position.Y);
				SendPosition(new Point(paddle.X, paddle.Y));
			}
		}

		private void paddle_PointerExited(object sender, PointerRoutedEventArgs e)
		{
			if(e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
				return;

			PointerPoint point = e.GetCurrentPoint(playingField);
			if (point.PointerId == _pointerIdPosition)
				_pointerIdPosition = 0;
		}

		void Main_KeyDown(CoreWindow sender, KeyEventArgs args)
		{
			switch(args.VirtualKey)
			{
				case VirtualKey.F1:
					if(AdminPanel.Visibility == Visibility.Collapsed)
						AdminPanel.Visibility = Visibility.Visible;
					else
						AdminPanel.Visibility = Visibility.Collapsed;
					break;
				case VirtualKey.F2:
					Reset();
					break;
				case VirtualKey.F3:
					Start();
					break;
				case VirtualKey.F8:
					SetScore(_testScore1, _testScore2);
					_testScore1++; _testScore2++;
					break;
				case VirtualKey.F9:
					SetVictoryDefeat(_testScore1, _testScore2);
					break;
			}
		}

		private async void Connect_Click(object sender, RoutedEventArgs e)
		{
			try
			{
				_socketClient = new SocketClient();
				_socketClient.PacketReceived += SocketClient_PacketReceived;

				if(_player == Player.One)
					await _socketClient.ConnectAsync(IpAddress.Text, Settings.ControllerPort_Player1);
				else if(_player == Player.Two)
					await _socketClient.ConnectAsync(IpAddress.Text, Settings.ControllerPort_Player2);
				else
					await new MessageDialog("Invalid player...make sure the Settings and IPs are correct!").ShowAsync();

				_connected = true;
				Status.Text = "Connected";
				AdminPanel.Visibility = Visibility.Collapsed;
				Window.Current.CoreWindow.PointerCursor = null;
			}
			catch(Exception ex)
			{
				// variable assignment just to remove compiler warning since we're not awaiting
				IAsyncOperation<IUICommand> x = new MessageDialog(ex.ToString()).ShowAsync();
			}
		}

		private async void SendPosition(Point p)
		{
			if(_socketClient != null && _connected)
			{
				UserInputPacket packet = new UserInputPacket { Command = UserInputCommand.Position };
				if(_player == Player.One)
//					packet.Position = new Point3((float)(p.X / (FullRight - FullLeft)), (float)(p.Y / (FullBottom - FullTop)), 0);
					packet.Position = new Point3( 
						(float)((p.X - FullLeft - PaddleRadius) / (FullRight - FullLeft - PaddleRadius*2)),
						(float)((p.Y - FullTop - PaddleRadius) / (FullBottom - FullTop - PaddleRadius*2)),
						0);
				else if(_player == Player.Two)
					//packet.Position = new Point3((float)(1.0f - ( (p.X-FullLeft) / (FullRight - FullLeft))), (float)( (p.Y-FullTop) / (FullBottom - FullTop)), 0);
					packet.Position = new Point3(
						(float)(1.0 - ((p.X - FullLeft - PaddleRadius) / (FullRight - FullLeft - PaddleRadius * 2))),
						(float)((p.Y - FullTop - PaddleRadius) / (FullBottom - FullTop - PaddleRadius * 2)),
						0);

				await _socketClient.WritePacketAsync(packet);
			}
		}

		void SocketClient_PacketReceived(object sender, PacketReceivedEventArgs e)
		{
			switch(e.Packet.Type)
			{
				case PacketType.GameStatePacket:
					GameStatePacket gsp = (GameStatePacket)e.Packet;

					switch(gsp.GameState)
					{
						case GameState.Initializing:
							break;
						case GameState.WaitingRoom:
							Reset();
							break;
						case GameState.Countdown:
							Start();
							break;
						case GameState.GamePlaying:
							SetScore(gsp.Player1Score, gsp.Player2Score);
							break;
						case GameState.GameResults:
							SetVictoryDefeat(gsp.Player1Score, gsp.Player2Score);
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}
					break;

				default:
					throw new ArgumentOutOfRangeException();
			}
		}

		private void SetScore(int p1Score, int p2Score)
		{
			if(_player == Player.One)
			{
				if(p1Score > 9)
				{
					LeftDigit1.Visibility = Visibility.Visible;
					LeftDigit1.ShowNumber(1);
				}
				else if(p1Score <= 9)
					LeftDigit1.Visibility = Visibility.Collapsed;

				if(p2Score > 9)
				{
					RightDigit2.Visibility = Visibility.Visible;
					Canvas.SetLeft(RightDigit1, RightDigit1Double);
					Canvas.SetLeft(RightDigit2, RightDigit2Double);
					RightDigit2.ShowNumber(p2Score % 10);
					RightDigit1.ShowNumber(1);
				}
				else if (p2Score <= 9)
				{
					Canvas.SetLeft(RightDigit1, RightDigit1Single);
					Canvas.SetLeft(RightDigit2, RightDigit2Single);

					RightDigit2.Visibility = Visibility.Collapsed;
					RightDigit1.ShowNumber(p2Score % 10);
				}

				LeftDigit2.ShowNumber(p1Score % 10);
			}
			else if(_player == Player.Two)
			{
				if(p2Score > 9)
				{
					LeftDigit1.Visibility = Visibility.Visible;
					LeftDigit1.ShowNumber(1);
				}
				else if(p2Score <= 9)
					LeftDigit1.Visibility = Visibility.Collapsed;

				if(p1Score > 9)
				{
					RightDigit2.Visibility = Visibility.Visible;
					Canvas.SetLeft(RightDigit1, RightDigit1Double);
					Canvas.SetLeft(RightDigit2, RightDigit2Double);
					RightDigit2.ShowNumber(p1Score % 10);
					RightDigit1.ShowNumber(1);
				}
				else if (p1Score <= 9)
				{
					Canvas.SetLeft(RightDigit1, RightDigit1Single);
					Canvas.SetLeft(RightDigit2, RightDigit2Single);

					RightDigit2.Visibility = Visibility.Collapsed;
					RightDigit1.ShowNumber(p1Score % 10);
				}

				LeftDigit2.ShowNumber(p2Score % 10);
			}
		}

		private void SetVictoryDefeat(int p1Score, int p2Score)
		{
		}

		private Player GetPlayer()
		{
			Player player = Player.Invalid;

			IReadOnlyList<HostName> hosts = NetworkInformation.GetHostNames();
			foreach(HostName hn in hosts)
			{
				if(hn.CanonicalName == Settings.ControllerIp_Player1)
					player = Player.One;
				else if(hn.CanonicalName == Settings.ControllerIp_Player2)
					player = Player.Two;
			}

			return player;
		}
	}
}
